package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    public Goods buildGoods(SpuDTO spuDTO){
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime()); //时间的毫秒值
        String all = spuDTO.getName()+spuDTO.getBrandName()+spuDTO.getCategoryName();
        goods.setAll(all);  //商品名称 、 商品的品牌名称 、 商品的分类名称
        goods.setSubTitle(spuDTO.getSubTitle());
//        当前spu的sku列表呢
        List<SkuDTO> skuDTOList = itemClient.findSkuListBySpuId(spuDTO.getId());

        List<Map> skus = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map map = new HashMap();
            map.put("id",skuDTO.getId());
            map.put("title",skuDTO.getTitle());
            map.put("price",skuDTO.getPrice());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(),",")); //取第一张图片
//            map.put("image", skuDTO.getImages().split(",")[1]);
            skus.add(map);
        }
        goods.setSkus(JsonUtils.toString(skus));
//        [
//               { id:1  title:"AAAA" price:100, image:""},
//               { id:2  title:"BBB" price:100, image:""},
//               { id:3  title:"CCC" price:200, image:""},
//               { id:4 title:"DDD" price:100, image:""},
//          ]
        //收集每个sku的价格放入到一个set集合中
        Set<Long> priceSet = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());
        goods.setPrice(priceSet);  //[100,200]

        SpuDetailDTO spuDetail = itemClient.findSpuDetailBySpuId(spuDTO.getId());
//        需要SPUDetail对象
        String genericSpec = spuDetail.getGenericSpec();//{"1":"华为（HUAWEI）","2":"8","3":2016.0,"5":189,"6":"","7":"Android"}
//        为了方便取值，装成map
//        Map<Long,String> genericSpecMap = JsonUtils.toBean(genericSpec, Map.class);
        Map<Long,String> genericSpecMap = JsonUtils.toMap(genericSpec, Long.class,String.class);

        String specialSpec = spuDetail.getSpecialSpec();//{"4":["魅海蓝","流光金","幻夜黑","珠光白","樱语粉"],"12":["4GB"],"13":["64GB"]}
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<String>>>() {});
//        {"1":"华为（HUAWEI）","2":"8","3":2016.0,"5":189,"6":"","7":"Android"}
//        把 spuDetail的GenericSpec和spuDetail的SpecialSpec合并到一个map中，在合并时把其中的规格参数id改成规格参数名称
        Map<String,Object> specsMap = new HashMap<>();


//        那么有多少的规格参数？当前商品分类下有多少规格参数就应该放入多少对数据
//        根据分类id查询规格参数
        List<SpecParamDTO> specParamList = itemClient.findSpecParamByCategoryIdOrGroupId(null, spuDTO.getCid3(), true);//只会把用来做搜索的规格参数放入到ES中

        for (SpecParamDTO specParam : specParamList) {
            String key = specParam.getName();
            Object value = "";
            if(specParam.getGeneric()){
                value = genericSpecMap.get(specParam.getId());
            }else{
                value = specialSpecMap.get(specParam.getId());
            }
            if(specParam.getIsNumeric()){
//                如果是数值型，需要凑成区间查询    0-1.0,1.0-1.5,1.5-2.0,2.0-2.5,2.5-
                 value = chooseSegment(value,specParam);
            }
            specsMap.put(key,value); //放入多少对数据呢？  有多少的规格参数就应该放多少对数据
        }
        goods.setSpecs(specsMap); // {"屏幕尺寸":"6.0","操作系统":"IOS"}
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    @Autowired
    private  ElasticsearchTemplate esTemplate;

    public PageResult<GoodsDTO> findGoodsByPage(SearchRequest searchRequest) {
        String key = searchRequest.getKey();
//        做一个判断是否有关键字
        if(StringUtils.isEmpty(key)){
            return null;
        }
//        GET /leyou/_search
//        {
//            "_source": {
//            "includes": ["id","subTitle","skus"]
//             } ,
//
//            "query": {
//            "match": {
//                "all": "手机"
//              }
//              },
//            "from": 0,
//            "size": 20
//        }
//        根据关键字 match 查询，查询的结果只要 id subTitle skus
//        Es和spring整合的 SpringDataElasticSearch
//        SpringDataElasticSearch结合ES原生的查询方式
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //设置显示字段的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));
//        根据关键字match查询
        buildBasicQuery(searchRequest, queryBuilder);
//        设置分页 起始位置不是当前页码 和 每页显示条数
        Integer page = searchRequest.getPage();
        Integer size = searchRequest.getSize();
//        PageRequest.of(page-1,size, Sort.Direction.DESC,"createTime");

        queryBuilder.withPageable(PageRequest.of(page-1,size, Sort.Direction.DESC,"createTime")) ;//封装了 "from" 和  "size"

//        SortBuilders.
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        List<Goods> goodsList = aggregatedPage.getContent();//当前页的数据
        if(CollectionUtils.isEmpty(goodsList)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        long total = aggregatedPage.getTotalElements(); //总条数
        Integer totalPages = aggregatedPage.getTotalPages(); //总页数
        return new PageResult<GoodsDTO>(total,totalPages.longValue(),goodsDTOList);
    }

    private void buildBasicQuery(SearchRequest searchRequest, NativeSearchQueryBuilder queryBuilder) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        构建关键字和过滤条件
        boolQuery.must(QueryBuilders.matchQuery("all",searchRequest.getKey()));

        Map<String, Object> filterMap = searchRequest.getFilterMap();
        if(filterMap!=null){
            //        遍历filterMap
            for (String key : filterMap.keySet()) {
                if(key.equals("品牌")){
                    boolQuery.filter(QueryBuilders.termQuery("brandId",filterMap.get(key)));
                }else  if(key.equals("分类")){
                    boolQuery.filter(QueryBuilders.termQuery("categoryId",filterMap.get(key)));
                }else {
//                    所有的规格参数
                    boolQuery.filter(QueryBuilders.termQuery("specs."+key,filterMap.get(key)));
                }
            }
        }
        queryBuilder.withQuery(boolQuery);
    }

    public Map<String, List<?>> filter(SearchRequest searchRequest) {
        Map<String, List<?>> resultMap = new HashMap<String, List<?>>();

        String key = searchRequest.getKey();
//        做一个判断是否有关键字
        if(StringUtils.isEmpty(key)){
            return null;
        }
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //设置显示字段的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(null,null));
//        根据关键字match查询
        buildBasicQuery(searchRequest, queryBuilder);
//        设置分页 起始位置不是当前页码 和 每页显示条数
        Integer page = searchRequest.getPage();
        queryBuilder.withPageable(PageRequest.of(page-1,1)) ;//封装了 "from" 和  "size"

//        构建聚合的条件
//        aggs": {
//        "brandAgg": {
//            "terms": {
//                "field": "brandId",
//                        "size": 20
//            }
//        }
        queryBuilder.addAggregation( AggregationBuilders.terms("brandAgg").field("brandId").size(20));

        queryBuilder.addAggregation( AggregationBuilders.terms("categoryAgg").field("categoryId").size(20));

        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = aggregatedPage.getAggregations();
//        从聚合结果中获取品牌
        Terms termsBrand = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> bucketsBrand = termsBrand.getBuckets();
        List<Long> brandIdList = bucketsBrand.stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
//        还需要根据品牌id集合查询品牌对象集合  通过feign的方式调用itemClient的接口
        List<BrandDTO> brandList = itemClient.findBrandListByIds(brandIdList);
        resultMap.put("品牌",brandList);
//        从聚合结果中获取分类
        Terms termsCategory = aggregations.get("categoryAgg");
        List<? extends Terms.Bucket> bucketsCategory = termsCategory.getBuckets();
        List<Long> categoryIdList = bucketsCategory.stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
//        还需要根据分类id集合查询分类对象集合  通过feign的方式调用itemClient的接口
        List<CategoryDTO> categoryList = itemClient.findCategoryListByIds(categoryIdList);
        resultMap.put("分类",categoryList);

       /* =========聚合规格参数======
                第一步：需要有分类
                第二步：根据分类查询需要搜索的规格参数
                第三步：构建聚合条件
                第四步：执行查询
                第五步：获取聚合后的规格参数结果*/
       if(categoryIdList!=null&&categoryIdList.size()>0){
           Long categoryId = categoryIdList.get(0);
           List<SpecParamDTO> paramList = itemClient.findSpecParamByCategoryIdOrGroupId(null, categoryId, true);
//           重新执行查询，重新构建条件
           NativeSearchQueryBuilder queryParamBuilder = new NativeSearchQueryBuilder();
           queryParamBuilder.withSourceFilter(new FetchSourceFilter(null,null)); //不显示goods的属性
//        根据关键字match查询
           buildBasicQuery(searchRequest, queryParamBuilder); //执行了基本查询
           queryParamBuilder.withPageable(PageRequest.of(0,1)) ;//封装了 "from" 和  "size"
//           真正的构建聚合条件
           for (SpecParamDTO specParam : paramList) {
               String paramName = specParam.getName();
               queryParamBuilder.addAggregation(AggregationBuilders.terms(paramName+"Agg").field("specs."+paramName).size(20));
           }
//           执行查询
           AggregatedPage<Goods> aggregatedParamPage = esTemplate.queryForPage(queryParamBuilder.build(), Goods.class);
           Aggregations paramPageAggregations = aggregatedParamPage.getAggregations();
//           获取聚合后的结果
           for (SpecParamDTO specParam : paramList) {
               String paramName = specParam.getName();
               Terms terms = paramPageAggregations.get(paramName + "Agg");
               List<? extends Terms.Bucket> buckets = terms.getBuckets();
               List<String> paramAggList = buckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
               resultMap.put(paramName,paramAggList);
           }
       }
        return resultMap;
    }

    @Autowired
    private GoodsRepository goodsRepository;

    public void createGoods(Long spuId) {
//        根据SpuId查询spu对象，构建goods
        SpuDTO spu = itemClient.findSpuBySpuId(spuId);
//        此时spu中brandName 和 categorysName为空
        BrandDTO brand = itemClient.findBrandById(spu.getBrandId());
        spu.setBrandName(brand.getName());
        List<CategoryDTO> categoryList = itemClient.findCategoryListByIds(spu.getCategoryIds());
        String categoryNames = categoryList.stream().map(CategoryDTO::getName).collect(Collectors.joining("/"));
        spu.setCategoryName(categoryNames);

        Goods goods = this.buildGoods(spu);
        goodsRepository.save(goods);

        System.out.println("索引数据创建成功");
    }

    public void removeGoods(Long spuId) {
        goodsRepository.deleteById(spuId);
        System.out.println("索引数据删除成功");
    }
}
